package com.cellulam.framework.scaffold.generate;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.cellulam.framework.scaffold.generate.db.TableMetaInfo;
import com.cellulam.framework.scaffold.generate.utils.GenUtils;
import com.cellulam.framework.scaffold.generate.db.ConnHelper;
import com.cellulam.framework.scaffold.generate.db.EnumValues;
import com.cellulam.framework.scaffold.generate.db.JdbcConfig;
import com.cellulam.framework.scaffold.generate.project.MavenModuleInfo;
import com.cellulam.framework.scaffold.generate.project.MavenProjectInfo;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class TemplateGenerator {

    private JdbcConfig jdbcConfig;
    private List<String> tables;
    private MavenProjectInfo projectInfo;

    private MavenModuleInfo supportModule;
    private MavenModuleInfo dtoModule;
    private MavenModuleInfo daoModule;
    private MavenModuleInfo serviceModule;
    private MavenModuleInfo controllerModule;

    private String serviceFileName = "service.ftl";

    public TemplateGenerator(JdbcConfig jdbcConfig, MavenProjectInfo projectInfo, List<String> tables) {
        this.jdbcConfig = jdbcConfig;
        this.projectInfo = projectInfo;
        this.tables = tables;

        supportModule = new MavenModuleInfo("support", projectInfo);
        dtoModule = new MavenModuleInfo("support", projectInfo);
        daoModule = new MavenModuleInfo("dao", "dao", projectInfo);
        serviceModule = new MavenModuleInfo("service", "service.services", projectInfo);
        controllerModule = new MavenModuleInfo("web", "web.controller.api" , projectInfo);
    }

    /** 是否强制覆盖DtoRequest文件, 默认: false */
    public boolean forceOverrideDtoRequest = false;
    /** 是否强制覆盖DtoResponse文件, 默认: false */
    public boolean forceOverrideDtoResponse = false;
    /** 是否强制覆盖Bean文件, 默认: true */
    public boolean forceOverrideBean = true;
    /** 是否强制覆盖Dao文件, 默认: false */
    public boolean forceOverrideDao = false;
    /** 是否强制覆盖Service文件, 默认: false */
    public boolean forceOverrideService = false;
    /** 是否强制覆盖BaseMapper文件, 默认: true */
    public boolean forceOverrideBaseMapper = true;

    /** 是否生成Controller文件, 默认: false */
    public boolean enableController = false;

    /** 是否启用使用LocalDateTime, 默认: false */
    public boolean enableLocalDateTime = false;

    /** 是否启用对于非空字段使用元数据类型, 默认: false. 注意: 若启用元数据类型, 一定要使用自己定义的查询 */
    public boolean enablePrimitiveType = false;

    public boolean enableTypeHandler = false;

    public List<String> removePrefixes = Lists.newArrayList();

    private static final Map<String, String> dbKeywords = new HashMap<>();
    static {
        dbKeywords.put("user", "user");
    }
    private String getSqlTableName(String tableName) {
        if (!dbKeywords.containsKey(tableName)) {
            return tableName;
        }
        if (jdbcConfig.getJdbcDriver().contains("sqlserver")) {
            return "[" + tableName + "]";
        } else if (jdbcConfig.getJdbcDriver().contains("mysql")) {
            return "`" + tableName + "`";
        }
        return tableName;
    }

    public void setSupportModule(MavenModuleInfo supportModule) {
        this.supportModule = supportModule;
    }

    public void setDaoModule(MavenModuleInfo daoModule) {
        this.daoModule = daoModule;
    }

    public void setServiceModule(MavenModuleInfo serviceModule) {
        this.serviceModule = serviceModule;
    }

    public void setControllerModule(MavenModuleInfo controllerModule) {
        this.controllerModule = controllerModule;
    }

    public void setServiceFileName(String serviceFileName) {
        this.serviceFileName = serviceFileName;
    }

    public void setDtoModule(MavenModuleInfo dtoModule) {
        this.dtoModule = dtoModule;
    }

    public void start() throws Exception {
        // MavenModuleInfo module, String ftlResourcePath, String targetFileName

        String dbName = jdbcConfig.getDbName();
        String dbPackageName = jdbcConfig.getDbPackageName();

        Map<String, TableMetaInfo> jsonTypes = Maps.newHashMap();

        for (String tbl : tables) {
            GenUtils.info("generate execute for table[" + tbl + "]", "");
            TableMetaInfo tableInfo = ConnHelper.instance.buildTableInfo(supportModule.copyWithPackage("model.bo." + dbPackageName + ".extra").getPackage(), dbName, tbl, enableLocalDateTime, enablePrimitiveType);
            tableInfo.setRemovePrefix(getRemovePrefix(tableInfo.getTableName()));
            tableInfo.setSqlTableName(getSqlTableName(tableInfo.getTableName()));
            tableInfo.setEnableLocalDateTime(enableLocalDateTime);
            tableInfo.setEnablePrimitiveType(enablePrimitiveType);

            String beanName = tableInfo.getBeanName();

            if(tableInfo.getJsonTypes() != null && !tableInfo.getJsonTypes().isEmpty()){
                tableInfo.getJsonTypes().forEach(x -> jsonTypes.put(x, tableInfo));
            }

            if(tableInfo.getEnumComments() != null && !tableInfo.getEnumComments().isEmpty()){
                for (String enumComment : tableInfo.getEnumComments()){

                    Pattern pattern = Pattern.compile("(.*),.*:(.*),(.*)");
                    Matcher matcher = pattern.matcher(enumComment);
                    if(matcher.matches()){
                        String comment = matcher.group(1);
                        String enumName = matcher.group(2);
                        String values = matcher.group(3);

                        String[] enumValues = values.split(";");
                        List<EnumValues> fields = Lists.newArrayList();

                        for (String value : enumValues){
                            String[] v = value.split(":");
                            fields.add(EnumValues.builder()
                                    .name(v[1].trim().toUpperCase())
                                    .description(v[2].trim())
                                    .value(v[0].trim())
                                    .build());
                        }

                        FtlProcessor enumProcessor = new FtlProcessor(supportModule.copyWithPackage("enums." + dbPackageName),
                                "enum.ftl",
                                enumName + ".java",
                                true);
                        enumProcessor.addRootMap("supportModel", supportModule.getPackage());
                        enumProcessor.addRootMap("enumComment", comment);
                        enumProcessor.addRootMap("enumName", enumName);
                        enumProcessor.addRootMap("enumValues", fields);
                        enumProcessor.process(tableInfo);
                    }

                }
            }

            String placeholderTest = dtoModule.copyWithPackage("model").fullPackagePath() + "/PlaceholderTest.java";

            File placeholderTestFile = new File(placeholderTest);
            if(placeholderTestFile.exists()){
                placeholderTestFile.delete();
            }

            new FtlProcessor(dtoModule.copyWithPackage("model.dto." + dbPackageName),
                    "dto_request.ftl",
                    beanName + "Request.java",
                    forceOverrideDtoRequest).process(tableInfo);

            new FtlProcessor(dtoModule.copyWithPackage("model.dto." + dbPackageName),
                    "dto_response.ftl",
                    beanName + "Response.java",
                    forceOverrideDtoResponse)
                    .process(tableInfo);

            new FtlProcessor(daoModule.copyWithPackage("model.po." + dbPackageName),
                    "bean.ftl",
                    beanName + "Bean.java",
                    forceOverrideBean)
                    .process(tableInfo);

            new FtlProcessor(daoModule.copyWithPackage("daos." + dbPackageName),
                    "dao.ftl",
                    beanName + "Dao.java",
                    forceOverrideDao)
                    .addProperty("poModule", daoModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("baseDaoModule", daoModule.copyWithPackage("daos.base"))
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .process(tableInfo);

            new FtlProcessor(serviceModule.copyWithPackage(dbPackageName),
                    serviceFileName,
                    beanName + "Service.java",
                    forceOverrideService)
                    .addProperty("supportModule", supportModule.getPackage())
                    .addProperty("beanModule", daoModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("dtoModule", dtoModule.copyWithPackage("model.dto." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage("daos." + dbPackageName))
                    .addProperty("baseService", serviceModule.copyWithPackage("base."))
                    .process(tableInfo);

            new FtlProcessor(daoModule.copyWithPackage(dbPackageName),
                    "mapper.ftl",
                    "Base" + beanName + "Mapper.xml",
                    forceOverrideBaseMapper)
                    .addProperty("supportCommonModule", supportModule.copyWithPackage("common"))
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("poModule", daoModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage("daos." + dbPackageName))
                    .useResourcePath("mapper/" + dbPackageName + "/base/")
                    .process(tableInfo);

            new FtlProcessor(daoModule.copyWithPackage(dbPackageName),
                    "mapper_empty.ftl",
                    beanName + "Mapper.xml",
                    false)
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("poModule", daoModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage("daos." + dbPackageName))
                    .useResourcePath("mapper/" + dbPackageName)
                    .process(tableInfo);

            if (enableController) {
                new FtlProcessor(controllerModule,
                        "controller.ftl",
                        beanName + "Controller.java",
                        false)
                        .addProperty("webModule", new MavenModuleInfo("web", "web", projectInfo).getPackage())
                        .addProperty("serviceModule", serviceModule.copyWithPackage(dbPackageName))
                        .addProperty("dtoModule", dtoModule.copyWithPackage("model.dto." + dbPackageName))
                        .addProperty("supportModule", supportModule)
                        .process(tableInfo);
            }

        }

        List<String> extraList = Lists.newArrayList();
        List<String> importExtraList = Lists.newArrayList();

        importExtraList.add("org.apache.ibatis.session.Configuration");
        importExtraList.add("org.apache.ibatis.type.TypeHandlerRegistry");
        importExtraList.add("org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer");
        importExtraList.add("org.springframework.stereotype.Component");

        TableMetaInfo tableMetaInfo = null;

        if(!jsonTypes.isEmpty()) {
            for (String jsonType : jsonTypes.keySet()) {
                FtlProcessor extraBeanProcessor = new FtlProcessor(supportModule.copyWithPackage("model.bo." + dbPackageName + ".extra"),
                        "extra_model.ftl",
                        jsonType + ".java",
                        false);
                extraBeanProcessor.addRootMap("extraModelName", jsonType);
                extraBeanProcessor.process(jsonTypes.get(jsonType));
                tableMetaInfo = jsonTypes.get(jsonType);

                extraList.add(jsonType);
                importExtraList.add(supportModule.getPackage("model.bo." + dbPackageName + ".extra." + jsonType));
            }
        }

        if(enableTypeHandler && tableMetaInfo != null) {
            String handlerPath = daoModule.copyWithPackage("daos.handlers").fullPackagePath() + "/" + "BeanTypeHandler.java";
            File file = new File(handlerPath);
            if(file.exists()){
                Pattern importPattern = Pattern.compile("import (.*);");
                Pattern registerPattern = Pattern.compile("registry\\.register\\((.*)\\.class,.*\\)");

                BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
                String line;
                while ((line = bufferedReader.readLine()) != null){
                    Matcher matcher = importPattern.matcher(line);
                    if(matcher.find()){
                        importExtraList.add(matcher.group(1).trim());
                    }

                    Matcher matcher2 = registerPattern.matcher(line);
                    if(matcher2.find()){
                        extraList.add(matcher2.group(1).trim());
                    }
                }
                bufferedReader.close();

                importExtraList = importExtraList.stream().distinct().collect(Collectors.toList());
                extraList = extraList.stream().distinct().collect(Collectors.toList());
            }

            new FtlProcessor(daoModule.copyWithPackage("daos.handlers"),
                    "type_handler.ftl",
                    "BeanTypeHandler.java",
                    forceOverrideBaseMapper)
                    .addProperty("importExtraList", importExtraList)
                    .addProperty("extraList", extraList)
                    .process(tableMetaInfo);
        }
    }


    public void clean() throws Exception {
        String dbName = jdbcConfig.getDbName();
        String dbPackageName = jdbcConfig.getDbPackageName();

        for (String tbl : tables) {
            GenUtils.info("generate execute for table[" + tbl + "]", "");
            TableMetaInfo tableInfo = ConnHelper.instance.buildTableInfo(null, dbName, tbl);
            tableInfo.setRemovePrefix(getRemovePrefix(tableInfo.getTableName()));
            tableInfo.setSqlTableName(getSqlTableName(tableInfo.getTableName()));

            String beanName = tableInfo.getBeanName();

            new FtlProcessor(dtoModule.copyWithPackage("model.dto." + dbPackageName),
                    "dto_request.ftl",
                    beanName + "Request.java",
                    forceOverrideDtoRequest).delete();

            new FtlProcessor(dtoModule.copyWithPackage("model.dto." + dbPackageName),
                    "dto_response.ftl",
                    beanName + "Response.java",
                    forceOverrideDtoResponse)
                    .delete();

            new FtlProcessor(daoModule.copyWithPackage("po." + dbPackageName),
                    "bean.ftl",
                    beanName + "Bean.java",
                    forceOverrideBean)
                    .delete();

            new FtlProcessor(daoModule.copyWithPackage(dbPackageName),
                    "dao.ftl",
                    beanName + "Dao.java",
                    forceOverrideDao)
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .delete();

            new FtlProcessor(serviceModule.copyWithPackage(dbPackageName),
                    "service.ftl",
                    beanName + "Service.java",
                    forceOverrideService)
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("dtoModule", dtoModule.copyWithPackage("model.dto." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage(dbPackageName))
                    .delete();

            new FtlProcessor(daoModule.copyWithPackage(dbPackageName),
                    "mapper.ftl",
                    "Base" + beanName + "Mapper.xml",
                    forceOverrideBaseMapper)
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage(dbPackageName))
                    .useResourcePath("mapper/" + dbPackageName + "/base/")
                    .delete();

            new FtlProcessor(daoModule.copyWithPackage(dbPackageName),
                    "mapper_empty.ftl",
                    beanName + "Mapper.xml",
                    false)
                    .addProperty("supportModule", supportModule.copyWithPackage("model.po." + dbPackageName))
                    .addProperty("daoModule", daoModule.copyWithPackage(dbPackageName))
                    .useResourcePath("mapper/" + dbPackageName)
                    .delete();

            if (enableController) {
                new FtlProcessor(controllerModule.copyWithPackage(dbPackageName),
                        "controller.ftl",
                        beanName + "Controller.java",
                        false)
                        .addProperty("serviceModule", serviceModule.copyWithPackage(dbPackageName))
                        .addProperty("dtoModule", dtoModule.copyWithPackage("model.dto." + dbPackageName))
                        .delete();
            }

        }

    }

    private void byProcess(MavenModuleInfo module, String ftlName, String targetFileName, boolean doOverride,
                           Map<String, Object> props, TableMetaInfo tableInfo, String resourcePath) throws Exception {
        FtlProcessor processor = new FtlProcessor(module, ftlName, targetFileName, doOverride);
        if (props != null) {
            for (Map.Entry<String, Object> ent : props.entrySet()) {
                processor.addProperty(ent.getKey(), ent.getValue());
            }
        }
        if (StringUtils.isNotBlank(resourcePath)) {
            processor.useResourcePath(resourcePath);
        }

        processor.process(tableInfo);
    }

    private String getRemovePrefix(String tableName) {
        if (removePrefixes == null || removePrefixes.isEmpty() || StringUtils.isEmpty(tableName)) {
            return null;
        }
        for (String re : removePrefixes) {
            if (tableName.startsWith(re)) {
                return re;
            }
        }
        return null;
    }
}
